﻿
Imports System.IO
Imports std
Imports errtype = errorHandle.errorType

Namespace storageJobConsumerInternal
    Partial Public MustInherit Class diskslice(Of ATOM_T As atom)
        Implements iasyncslice

        Private Const NO_STREAM_BUFFER As Int32 = 8
        Private Const DEFAULT_STREAM_BUFFER As Int32 = 0
        Protected Shared ReadOnly needtrace As Boolean = False
        Private Shared ReadOnly encoding As Text.Encoding = Nothing
        Private Shared ReadOnly DISKOPER_TIME_MS As Int64 = 0
        Private ReadOnly readMeta As Boolean = False
        Private ReadOnly modifiable As Boolean = False

        Shared Sub New()
            encoding = Global.encoding.utf8()
            needtrace = isdebugmode() OrElse
                        envs.env_value(New String() {"diskslice_trace",
                                                     "diskslicetrace",
                                                     "DISKSLICE_TRACE",
                                                     "DISKSLICETRACE"}, Nothing)
            initializers = create_initializers()
            DISKOPER_TIME_MS = counter.registerAverageAndLastAverage(
                                        strcat(strtoupper(GetType(diskslice(Of ATOM_T)).Name()), "_DISKOPER_TIME_MS"))
        End Sub

        Private ReadOnly index As map(Of String, ATOM_T) = Nothing

        Protected Sub New(ByVal readMeta As Boolean, ByVal modifiable As Boolean)
            assert(supportedVersion(version()))
            index = New map(Of String, ATOM_T)()
            Me.readMeta = readMeta
            Me.modifiable = modifiable
        End Sub

        Protected Sub New(ByVal readMetaAndModifiable As Boolean)
            Me.New(readMetaAndModifiable, readMetaAndModifiable)
        End Sub

        Private _memoryBuffSize As Int32 = npos
        Protected Property memoryBuffSize() As Int32
            Get
                assert(_memoryBuffSize <> npos)
                Return _memoryBuffSize
            End Get
            Private Set(ByVal value As Int32)
                assert(_memoryBuffSize = npos)
                assert(value <> npos)
                _memoryBuffSize = value
            End Set
        End Property

        Private _datafullpath As String = Nothing
        Protected Property datafullpath() As String
            Get
                assert(Not isemptystring(_datafullpath))
                Return _datafullpath
            End Get
            Private Set(ByVal value As String)
                assert(isemptystring(_datafullpath))
                assert(Not isemptystring(value))
                copy(_datafullpath, value)
            End Set
        End Property

        Private _indexfilefullpath As String = Nothing
        Protected Property indexfilefullpath() As String
            Get
                assert(Not isemptystring(_indexfilefullpath))
                Return _indexfilefullpath
            End Get
            Private Set(ByVal value As String)
                assert(_indexfilefullpath Is Nothing)
                assert(Not isemptystring(value))
                copy(_indexfilefullpath, value)
            End Set
        End Property

        Private _indexfile As String = Nothing
        Protected Property indexfile() As String
            Get
                assert(Not isemptystring(_indexfile))
                Return _indexfile
            End Get
            Private Set(ByVal value As String)
                assert(_indexfile Is Nothing)
                assert(Not isemptystring(value))
                copy(_indexfile, value)
            End Set
        End Property

        Private _maxAtomsCount As UInt64 = 0
        Protected Property maxAtomsCount() As UInt64
            Get
                assert(_maxAtomsCount > 0)
                Return _maxAtomsCount
            End Get
            Private Set(ByVal value As UInt64)
                assert(_maxAtomsCount = 0)
                assert(value > 0)
                _maxAtomsCount = value
            End Set
        End Property

        Private _maxIndexSize As UInt64 = 0
        Protected Property maxIndexSize() As UInt64
            Get
                assert(_maxIndexSize > 0)
                Return _maxIndexSize
            End Get
            Private Set(ByVal value As UInt64)
                assert(_maxIndexSize = 0)
                assert(value > 0)
                _maxIndexSize = value
            End Set
        End Property

        Private _preservedDriveSpace As UInt64 = 0
        Protected Property preservedDriveSpace() As UInt64
            Get
                assert(_preservedDriveSpace > 0)
                Return _preservedDriveSpace
            End Get
            Private Set(ByVal value As UInt64)
                assert(_preservedDriveSpace = 0)
                assert(value > 0)
                _preservedDriveSpace = value
            End Set
        End Property

        Protected Function shareDriveDisksliceCount() As Int32
            Return diskslice.shareDriveDisksliceCount(datafullpath())
        End Function

        Protected Function driveFreeSpace() As Int64
            Dim rtn As Int64 = 0
            rtn = diskslice.driveFreeSpace(datafullpath())
            If rtn < preservedDriveSpace() Then
                Return 0
            Else
                Return rtn - preservedDriveSpace()
            End If
        End Function

        Private _stream As Stream = Nothing
        Private _reader As BinaryReader = Nothing
        Private _writer As BinaryWriter = Nothing

        Protected Function opened() As Boolean
            Return Not _stream Is Nothing AndAlso
                   Not _reader Is Nothing AndAlso
                   Not _writer Is Nothing AndAlso
                   _stream.CanRead() AndAlso
                   _stream.CanWrite()
        End Function

        Protected Function stream() As Stream
            assert(Not _stream Is Nothing)
            Return _stream
        End Function

        Protected Function reader() As BinaryReader
            assert(Not _reader Is Nothing)
            Return _reader
        End Function

        Protected Function writer() As BinaryWriter
            assert(Not _writer Is Nothing)
            Return _writer
        End Function

        Protected Function start_diskoper() As diskoper
            Dim rtn As diskoper = New diskoper()
            rtn.start()
            Return rtn
        End Function

        Protected Sub end_diskoper(ByRef i As diskoper)
            Dim duration As Int64 = 0
            If memoryBuffSize() = NO_STREAM_BUFFER Then
                stream.Flush()
                flush()
            End If
            i.end(duration)
            counter.increase(DISKOPER_TIME_MS, duration)
        End Sub

        Private Function close_steam_no_check() As Boolean
            Return closestream(_stream) AndAlso
                   closeBinaryWriter(_writer) AndAlso
                   closeBinaryReader(_reader)
        End Function

        Private Function close_stream() As Boolean
            Return opened() AndAlso close_steam_no_check()
        End Function

        Private Function open_stream() As Boolean
            assert(Not opened())
            If File.Exists(indexfilefullpath()) Then
                Try
                    If memoryBuffSize() = DEFAULT_STREAM_BUFFER Then
                        _stream = New FileStream(indexfilefullpath(),
                                                 FileMode.Open,
                                                 FileAccess.ReadWrite,
                                                 FileShare.Read)
                    Else
                        _stream = New FileStream(indexfilefullpath(),
                                                 FileMode.Open,
                                                 FileAccess.ReadWrite,
                                                 FileShare.Read,
                                                 memoryBuffSize())
                    End If
                    _reader = New BinaryReader(stream(), encoding)
                    _writer = New BinaryWriter(stream(), encoding)
                    Return True
                Catch ex As Exception
                    raiseError(errtype.exclamation, "cannot create stream / reader / writer, ex ", ex.Message)
                    close_steam_no_check()
                    Return False
                End Try
            Else
                Return False
            End If
        End Function

        Private Function check_version() As Boolean
            If opened() Then
                assert(stream().Seek(0, SeekOrigin.Begin) = 0)
                Dim s As String = Nothing
                If binaryReaderHelper.readString(reader(), s) Then
                    If supportedVersion(s) Then
                        Return True
                    Else
                        raiseError(errorHandle.errorType.warning,
                                   "version ", s, " is not supported by current diskslice ", Me.GetType().FullName())
                        Return False
                    End If
                Else
                    raiseError(errtype.exclamation,
                               "cannot get version info from file ",
                               indexfilefullpath(),
                               ", invalid or not supported slice file.")
                    Return False
                End If
            Else
                Return False
            End If
        End Function

        Private Function close() As Boolean
            Return before_close() AndAlso
                   close_stream() AndAlso
                   true_(Sub()
                             index.clear()
                         End Sub) AndAlso
                   after_close()
        End Function

        Private Function read_index(ByRef key As String, ByRef a As ATOM_T) As Boolean
            Dim start As UInt64 = 0
            Dim metaLength As UInt64 = 0
            Dim checksum As UInt64 = 0
            start = stream().Position()
            If binaryReaderHelper.readString(reader(), key) AndAlso
               binaryReaderHelper.readUInt64(reader(), metaLength) AndAlso
               binaryReaderHelper.readUInt64(reader(), checksum) AndAlso
               checksum = checksum_64 Then
                a = atom_ctor(start, stream().Position().asuint64(), metaLength)
                If readMeta Then
                    Dim failed As _do(Of String, String, Boolean) =
                        Function(ByRef s1 As String, ByRef s2 As String) As Boolean
                            raiseError(errtype.exclamation,
                                       "failed to read meta from file ", indexfilefullpath(),
                                       ", start ", start, ", position ", stream.Position(),
                                       ", length ", metaLength, s1, s2)
                            Return False
                        End Function
                    Dim meta As bytes = Nothing
                    meta = New bytes()
                    meta.size() = metaLength
                    Try
                        If reader().Read(+meta, 0, metaLength) = metaLength Then
                            Return a.read(meta)
                        Else
                            Return failed(Nothing, Nothing)
                        End If
                    Catch ex As Exception
                        Return failed(", ex ", ex.Message)
                    End Try
                Else
                    assert(stream().Seek(metaLength, SeekOrigin.Current) = a.end())
                    Return True
                End If
            Else
                raiseError(errtype.exclamation,
                           "failed to read key, length and checksum from file ", indexfilefullpath())
                Return False
            End If
        End Function

        Private Function open() As Boolean
            If before_open() AndAlso
               open_stream() Then
                If check_version() Then
                    Dim diskoper As diskoper = Nothing
                    diskoper = start_diskoper()
                    Dim key As String = Nothing
                    Dim a As ATOM_T = Nothing
                    Dim lastpos As Int64 = 0
                    While stream().Position() < stream().Length()
                        lastpos = stream().Position()
                        If read_index(key, a) Then
                            index(key) = a
                        Else
                            raiseError(errtype.exclamation,
                                       "failed to read index from file ", indexfilefullpath(),
                                       ", start ", lastpos, ", ignore following data")
                            Exit While
                        End If
                    End While
                    end_diskoper(diskoper)
                    If after_open() Then
                        Return True
                    Else
                        close()
                        Return False
                    End If
                Else
                    close()
                    Return False
                End If
            Else
                Return False
            End If
        End Function

        Private Function traversal_index(ByVal action As _do(Of String, ATOM_T, Boolean)) As Boolean
            assert(Not action Is Nothing)
            Dim it As map(Of String, ATOM_T).iterator = Nothing
            it = index.begin()
            While it <> index.end()
                If Not action((+it).first(), (+it).second) Then
                    Exit While
                End If
                it += 1
            End While

            Return it = index.end()
        End Function

        Private Function find_index(ByVal key As String,
                                    ByRef it As map(Of String, ATOM_T).iterator,
                                    ByRef atom As ATOM_T) As Boolean
            it = index.find(key)
            If it = index.end() Then
                atom = Nothing
                Return False
            Else
                atom = (+it).second
                Return True
            End If
        End Function

        Private Function find_index(ByVal key As String,
                                    ByRef atom As ATOM_T) As Boolean
            Return find_index(key, Nothing, atom)
        End Function

        Private Function create() As Boolean
            assert(Not opened())
            If before_create() Then
                Dim succ As Boolean = False
                Dim diskoper As diskoper = Nothing
                diskoper = start_diskoper()
                Try
                    Dim s As Stream = New FileStream(indexfilefullpath(),
                                                     FileMode.Create,
                                                     FileAccess.Write,
                                                     FileShare.Read)
                    Dim w As BinaryWriter = New BinaryWriter(s, encoding)
                    assert(s.Seek(0, SeekOrigin.Begin) = 0)
                    w.Write(version())
                    closestream(s)
                    closeBinaryWriter(w)
                    succ = True
                Catch ex As Exception
                    raiseError(errtype.exclamation,
                               "failed to create empty index to file ", indexfilefullpath(),
                               ", ex ", ex.Message)
                End Try
                end_diskoper(diskoper)
                Return succ AndAlso after_create()
            Else
                Return False
            End If
        End Function

        Private Function create_open() As Boolean
            Return create() AndAlso open()
        End Function
    End Class
End Namespace
